using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;

using Prota.Unity;
using UnityEditor;
using UnityEngine.Rendering;

namespace Prota.Unity
{
    public enum MaterialPropertyType
    {
        None,
        Vector4,
        Vector3,
        Vector2,
        Float,
        Color,
        Texture,
        Gradient,
    }
    
    
    
    [Serializable]
    public struct MaterialPropertyEntry
    {
        public string propertyName;
        public MaterialPropertyType propertyType;
        public readonly int propertyId => Shader.PropertyToID(propertyName);
        public Vector4 vectorValue;
        public Texture textureValue;
        
        public float floatValue
        {
            get => vectorValue.x;
            set => vectorValue.x = value;
        }
        
        public Color colorValue
        {
            get => vectorValue;
            set => vectorValue = value;
        }
        
        public MaterialPropertyEntry Clone()
        {
            return new MaterialPropertyEntry
            {
                propertyName = propertyName,
                propertyType = propertyType,
                vectorValue = vectorValue,
                textureValue = textureValue,
            };
        }
        
        public void SetPropertyTo(MaterialPropertyBlock props)
        {
            var propertyId = this.propertyId;
            switch(propertyType)
            {
                case MaterialPropertyType.Vector4:
                case MaterialPropertyType.Vector3:
                case MaterialPropertyType.Vector2:
                    props.SetVector(propertyId, vectorValue);
                    break;
                case MaterialPropertyType.Float:
                    props.SetFloat(propertyId, floatValue);
                    break;
                case MaterialPropertyType.Color:
                    props.SetColor(propertyId, colorValue);
                    break;
                case MaterialPropertyType.Texture:
                    if(textureValue == null) props.SetTexture(propertyId, Texture2D.whiteTexture);
                    else props.SetTexture(propertyId, textureValue);
                    break;
            }
        }
        
        public void SetPropertyTo(Material material)
        {
            switch(propertyType)
            {
                case MaterialPropertyType.Vector4:
                case MaterialPropertyType.Vector3:
                case MaterialPropertyType.Vector2:
                    material.SetVector(propertyId, vectorValue);
                    break;
                case MaterialPropertyType.Float:
                    material.SetFloat(propertyId, floatValue);
                    break;
                case MaterialPropertyType.Color:
                    material.SetColor(propertyId, colorValue);
                    break;
                case MaterialPropertyType.Texture:
                    material.SetTexture(propertyId, textureValue);
                    break;
            }
        }
        
        public void SetPropertyTo(CommandBuffer cmd)
        {
            switch(propertyType)
            {
                case MaterialPropertyType.Vector4:
                case MaterialPropertyType.Vector3:
                case MaterialPropertyType.Vector2:
                    cmd.SetGlobalVector(propertyId, vectorValue);
                    break;
                case MaterialPropertyType.Float:
                    cmd.SetGlobalFloat(propertyId, floatValue);
                    break;
                case MaterialPropertyType.Color:
                    cmd.SetGlobalColor(propertyId, colorValue);
                    break;
                case MaterialPropertyType.Texture:
                    cmd.SetGlobalTexture(propertyId, textureValue);
                    break;
            }
        }
        
        public void SetPropertyGlobally()
        {
            switch(propertyType)
            {
                case MaterialPropertyType.Vector4:
                case MaterialPropertyType.Vector3:
                case MaterialPropertyType.Vector2:
                    Shader.SetGlobalVector(propertyId, vectorValue);
                    break;
                case MaterialPropertyType.Float:
                    Shader.SetGlobalFloat(propertyId, floatValue);
                    break;
                case MaterialPropertyType.Color:
                    Shader.SetGlobalColor(propertyId, colorValue);
                    break;
                case MaterialPropertyType.Texture:
                    Shader.SetGlobalTexture(propertyId, textureValue);
                    break;
            }
        }
        
        public MaterialPropertyEntry FetchPropertyGlobally()
        {
            var res = this.Clone();
            switch(propertyType)
            {
                case MaterialPropertyType.Vector4:
                case MaterialPropertyType.Vector3:
                case MaterialPropertyType.Vector2:
                    res.vectorValue = Shader.GetGlobalVector(propertyId);
                    break;
                case MaterialPropertyType.Float:
                    res.floatValue = Shader.GetGlobalFloat(propertyId);
                    break;
                case MaterialPropertyType.Color:
                    res.colorValue = Shader.GetGlobalColor(propertyId);
                    break;
                case MaterialPropertyType.Texture:
                    res.textureValue = Shader.GetGlobalTexture(propertyId);
                    break;
            }
            return res;
        }
        
        public void UseDefaultValue()
        {
            switch(propertyType)
            {
                case MaterialPropertyType.Vector4:
                case MaterialPropertyType.Vector3:
                case MaterialPropertyType.Vector2:
                    vectorValue = Vector4.zero;
                    break;
                case MaterialPropertyType.Float:
                    floatValue = 0;
                    break;
                case MaterialPropertyType.Color:
                    colorValue = new Color(0, 0, 0, 0);
                    break;
                case MaterialPropertyType.Texture:
                    textureValue = null;
                    break;
            }
        }
    }
    
    [RequireComponent(typeof(Renderer))]
    [DisallowMultipleComponent]
    [ExecuteAlways]
    public class MaterialPropertyProvider : MonoBehaviour
    {
        [NonSerialized] Renderer _rd;
        public Renderer rd => _rd != null ? _rd : _rd = this.GetComponent<Renderer>();
        
        public bool update = false;
        public bool clear = false;
        public bool always = false;
        
        public MaterialPropertyEntry[] properties = Array.Empty<MaterialPropertyEntry>();
        
        void Update()
        {
            if(clear) ClearValue();
            if(update) UpdateValue();
        }
        
        void ClearValue()
        {
            if(!always) clear = false;
            rd.SetPropertyBlock(null);
            switch(rd)
            {
                case SpriteRenderer srd:
                    if(srd.sprite != null)
                    {
                        srd.GetPropertyBlock(props);
                        props.SetTexture(Shader.PropertyToID("_MainTex"), srd.sprite.texture);
                        srd.SetPropertyBlock(props);
                    }
                    break;
            }
        }
        
        void UpdateValue()
        {
            if(!always) update = false;
            var rd = this.rd;
            if(!rd) return;
            
            rd.GetPropertyBlock(props);
            foreach(var p in properties) p.SetPropertyTo(props);
            rd.SetPropertyBlock(props);
        }
        
        
        // ====================================================================================================
        // ====================================================================================================
        
        static MaterialPropertyBlock props;
        
        [InitializeOnLoadMethod]
        [RuntimeInitializeOnLoadMethod]
        static void Initialize()
        {
            props = new MaterialPropertyBlock();
        }
        
        
    }
}
